﻿#if !WINDOWS_PHONE
using System;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Storage;
using System.IO;

namespace JGLib.Settings.Binary
{
    /// <summary>The class used to save and load properties from file storage.</summary>
    [System.Diagnostics.DebuggerNonUserCode]
    public class BinaryPropertyFile : XnaFile
    {
        private BinaryProperty[] Properties;

        /// <summary>Initializes an instance of a BinaryPropertyFile.</summary>
        /// <param name="propertiesFilePath">The path to the properties file.</param>
        /// <param name="properties">The list of properties that will be written and read from the properties file.</param>
        public BinaryPropertyFile(string propertiesFilePath, params BinaryProperty[] properties)
            : base(propertiesFilePath)
        {
            this.Properties = properties;
            this.Refresh();
        }

        protected override void WriteDefaultText(Stream stream)
        {
            using (BinaryWriter writer = new BinaryWriter(stream))
                foreach (BinaryProperty prop in this.Properties)
                    prop.WriteDefault(writer);
        }
        protected override void WriteText(Stream stream)
        {
            using (BinaryWriter writer = new BinaryWriter(stream))
                foreach (BinaryProperty prop in this.Properties)
                    prop.Write(writer);
        }
        protected override void ReadText(Stream stream)
        {
            using (BinaryReader reader = new BinaryReader(stream))
                foreach (BinaryProperty prop in this.Properties)
                    prop.Read(reader);
        }

        /// <summary> Sets the value of the property with the specified name. </summary>
        /// <typeparam name="T">The type of the value of the property.</typeparam>
        /// <param name="name">The name of the property.</param>
        /// <param name="value">The new value of the property.</param>
        public void Set<T>(string name, T value)
        {
            for (int i = 0; i < this.Properties.Length; i++)
            {
                if (this.Properties[i].Name == name)
                {
                    this.Properties[i].Value = value;
                    break;
                }
            }
            this.Flush();
        }
        /// <summary> Gets the value of the property with the specified name. </summary>
        /// <typeparam name="T">The type of the value of the property.</typeparam>
        /// <param name="name">The name of the property.</param>
        /// <returns></returns>
        public T Get<T>(string name)
        {
            for (int i = 0; i < this.Properties.Length; i++)
                if (this.Properties[i].Name == name)
                    return this.Properties[i].GetValue<T>();
            throw new ArgumentException("No property found with name '" + name + "'.(JGLib.Settings.Binary.BinaryPropertyFile.Get<T>(string)).", "name");
        }

        /// <summary> Resets the property with the specified name to its default value. </summary>
        /// <param name="name">The name of the property to reset.</param>
        public void ResetProperty(string name)
        {
            foreach (BinaryProperty prop in this.Properties)
                if (prop.Name == name)
                    prop.Reset();
            this.Flush();
        }
        /// <summary> Resets all properties to their default values. </summary>
        public void ResetAllProperties()
        {
            foreach (BinaryProperty prop in this.Properties)
                prop.Reset();
            this.Flush();
        }
    }

    /// <summary>The delegate type for writing data to file.</summary>
    /// <param name="writer">The BinaryWriter with which to write data.</param>
    /// <param name="value">The data to write with the BinaryWriter. Must be typecasted to a type that the BinaryWriter can write.</param>
    public delegate void PropertyWriter(BinaryWriter writer, object value);
    /// <summary>The delegate type for reading data from file.</summary>
    /// <param name="reader">The BinaryReader with which to read data.</param>
    /// <returns>The data that is read by the BinaryReader.</returns>
    public delegate object PropertyReader(BinaryReader reader);
    /// <summary>The structure used to represent a single property.</summary>
    [System.Diagnostics.DebuggerNonUserCode]
    public struct BinaryProperty
    {
        /// <summary>The name of this property.</summary>
        public string Name;
        internal object DefaultValue;
        internal object Value;

        private PropertyWriter Writer;
        private PropertyReader Reader;

        /// <summary>Initializes this Property with a name, a default value, and a data writer and reader for a common data type.</summary>
        /// <param name="name">The name of this property.</param>
        /// <param name="defaultValue">The default value of this property.</param>
        /// <param name="dataType">The data type that this property's reader and writer will use.</param>
        public BinaryProperty(string name, object defaultValue, CommonType dataType)
        {
            this.Name = name;
            this.DefaultValue = defaultValue;
            this.Value = defaultValue;
            switch (dataType)
            {
                case CommonType.Boolean:
                {
                    this.Reader = BinaryPropIO.BooleanReader;
                    this.Writer = BinaryPropIO.BooleanWriter;
                } break;
                case CommonType.Byte:
                {
                    this.Reader = BinaryPropIO.ByteReader;
                    this.Writer = BinaryPropIO.ByteWriter;
                } break;
                case CommonType.Char:
                {
                    this.Reader = BinaryPropIO.CharReader;
                    this.Writer = BinaryPropIO.CharWriter;
                } break;
                case CommonType.Decimal:
                {
                    this.Reader = BinaryPropIO.DecimalReader;
                    this.Writer = BinaryPropIO.DecimalWriter;
                } break;
                case CommonType.Double:
                {
                    this.Reader = BinaryPropIO.DoubleReader;
                    this.Writer = BinaryPropIO.DoubleWriter;
                } break;
                case CommonType.Short:
                {
                    this.Reader = BinaryPropIO.ShortReader;
                    this.Writer = BinaryPropIO.ShortWriter;
                } break;
                case CommonType.Int:
                {
                    this.Reader = BinaryPropIO.IntReader;
                    this.Writer = BinaryPropIO.IntWriter;
                } break;
                case CommonType.Long:
                {
                    this.Reader = BinaryPropIO.LongReader;
                    this.Writer = BinaryPropIO.LongWriter;
                } break;
                case CommonType.SByte:
                {
                    this.Reader = BinaryPropIO.SByteReader;
                    this.Writer = BinaryPropIO.SByteWriter;
                } break;
                case CommonType.Float:
                {
                    this.Reader = BinaryPropIO.FloatReader;
                    this.Writer = BinaryPropIO.FloatWriter;
                } break;
                case CommonType.String:
                {
                    this.Reader = BinaryPropIO.StringReader;
                    this.Writer = BinaryPropIO.StringWriter;
                } break;
                case CommonType.UShort:
                {
                    this.Reader = BinaryPropIO.UShortReader;
                    this.Writer = BinaryPropIO.UShortWriter;
                } break;
                case CommonType.UInt:
                {
                    this.Reader = BinaryPropIO.UIntReader;
                    this.Writer = BinaryPropIO.UIntWriter;
                } break;
                case CommonType.ULong:
                {
                    this.Reader = BinaryPropIO.ULongReader;
                    this.Writer = BinaryPropIO.ULongWriter;
                } break;
                default: throw new Exception("You reached a logically impossible point in the program. (JGLib.Settings.Binary.BinaryProperty.new(string, object, JGLib.Settings.CommonType)).");
            }
        }
        /// <summary>Initializes this Property with a name, a default value, a data writer delegate, and a data reader delegate.</summary>
        /// <param name="name">The name of this property.</param>
        /// <param name="defaultValue">The default value of this property.</param>
        /// <param name="writer">The delegate with which this property will write its data to file.</param>
        /// <param name="reader">The delegate with which this property will read its data from file.</param>
        public BinaryProperty(string name, object defaultValue, PropertyWriter writer, PropertyReader reader)
        {
            this.Name = name;
            this.DefaultValue = defaultValue;
            this.Value = defaultValue;
            this.Writer = writer;
            this.Reader = reader;
        }

        internal void Reset()
        {
            this.Value = this.DefaultValue;
        }

        internal T GetValue<T>()
        {
            return (T)this.Value;
        }
        /// <summary>Gets the default value of this property, typecasted to the specified type.</summary>
        /// <typeparam name="T">The type to typecast the default value to.</typeparam>
        /// <returns>The typecasted default value of this property.</returns>
        public T GetDefaultValue<T>()
        {
            return (T)this.DefaultValue;
        }

        internal void WriteDefault(BinaryWriter writer)
        {
            this.Writer(writer, this.DefaultValue);
        }
        internal void Write(BinaryWriter writer)
        {
            this.Writer(writer, this.Value);
        }
        internal void Read(BinaryReader reader)
        {
            this.Value = this.Reader(reader);
        }
    }

    /// <summary>The class that contains all readers and writers of common data types; to be used with the Property struct.</summary>
    [System.Diagnostics.DebuggerNonUserCode]
    public static class BinaryPropIO
    {
        public static PropertyWriter BooleanWriter = ((writer, value) => writer.Write((bool)value));
        public static PropertyReader BooleanReader = ((reader) => reader.ReadBoolean());

        public static PropertyWriter ByteWriter = ((writer, value) => writer.Write((byte)value));
        public static PropertyReader ByteReader = ((reader) => reader.ReadByte());

        public static PropertyWriter CharWriter = ((writer, value) => writer.Write((char)value));
        public static PropertyReader CharReader = ((reader) => reader.ReadChar());

        public static PropertyWriter DecimalWriter = ((writer, value) => writer.Write((decimal)value));
        public static PropertyReader DecimalReader = ((reader) => reader.ReadDecimal());

        public static PropertyWriter DoubleWriter = ((writer, value) => writer.Write((double)value));
        public static PropertyReader DoubleReader = ((reader) => reader.ReadDouble());

        public static PropertyWriter ShortWriter = ((writer, value) => writer.Write((short)value));
        public static PropertyReader ShortReader = ((reader) => reader.ReadInt16());

        public static PropertyWriter IntWriter = ((writer, value) => writer.Write((int)value));
        public static PropertyReader IntReader = ((reader) => reader.ReadInt32());

        public static PropertyWriter LongWriter = ((writer, value) => writer.Write((long)value));
        public static PropertyReader LongReader = ((reader) => reader.ReadInt64());

        public static PropertyWriter SByteWriter = ((writer, value) => writer.Write((sbyte)value));
        public static PropertyReader SByteReader = ((reader) => reader.ReadSByte());

        public static PropertyWriter FloatWriter = ((writer, value) => writer.Write((float)value));
        public static PropertyReader FloatReader = ((reader) => reader.ReadSingle());

        public static PropertyWriter StringWriter = ((writer, value) => writer.Write((string)value));
        public static PropertyReader StringReader = ((reader) => reader.ReadString());

        public static PropertyWriter UShortWriter = ((writer, value) => writer.Write((ushort)value));
        public static PropertyReader UShortReader = ((reader) => reader.ReadUInt16());

        public static PropertyWriter UIntWriter = ((writer, value) => writer.Write((uint)value));
        public static PropertyReader UIntReader = ((reader) => reader.ReadUInt32());

        public static PropertyWriter ULongWriter = ((writer, value) => writer.Write((ulong)value));
        public static PropertyReader ULongReader = ((reader) => reader.ReadUInt64());
    }

    /// <summary>Represents the common data types that are readable and writable by a BinaryReader and a BinaryWriter, respectively.</summary>
    public enum CommonType { Boolean, Byte, Char, Decimal, Double, Short, Int, Long, SByte, Float, String, UShort, UInt, ULong }
}
#endif